//--------------------------------------------------------------------------------------
// File: XboxDDSTextureLoader.cpp
//
// Functions for loading a DDS texture using the XBOX extended header and creating a
// Direct3D11.X runtime resource for it via the CreatePlacement APIs
//
// Note these functions will not load standard DDS files. Use the DDSTextureLoader
// module in the DirectXTex package or as part of the DirectXTK library to load
// these files which use standard Direct3D 11 resource creation APIs.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
// http://go.microsoft.com/fwlink/?LinkId=248929
//--------------------------------------------------------------------------------------

#include "pch.h"

#include "XboxDDSTextureLoader.h"

#include "dds.h"
#include "DirectXHelpers.h"
#include "PlatformHelpers.h"

#include <xdk.h>

using namespace DirectX;
using namespace Xbox;

//--------------------------------------------------------------------------------------
// DDS file structure definitions
//
// See DDS.h in the 'Texconv' sample and the 'DirectXTex' library
//--------------------------------------------------------------------------------------
#pragma pack(push,1)

struct DDS_HEADER_XBOX
    // Must match structure defined in xtexconv tool
{
    DXGI_FORMAT dxgiFormat;
    uint32_t    resourceDimension;
    uint32_t    miscFlag; // see DDS_RESOURCE_MISC_FLAG
    uint32_t    arraySize;
    uint32_t    miscFlags2; // see DDS_MISC_FLAGS2
    uint32_t    tileMode; // see XG_TILE_MODE
    uint32_t    baseAlignment;
    uint32_t    dataSize;
    uint32_t    xdkVer; // matching _XDK_VER
};

static_assert( sizeof(DDS_HEADER_XBOX) == 36, "DDS XBOX Header size mismatch");

#pragma pack(pop)

//--------------------------------------------------------------------------------------
static HRESULT LoadTextureDataFromFile( _In_z_ const wchar_t* fileName,
                                        std::unique_ptr<uint8_t[]>& ddsData,
                                        DDS_HEADER** header,
                                        uint8_t** bitData,
                                        size_t* bitSize
                                      )
{
    if (!header || !bitData || !bitSize)
    {
        return E_POINTER;
    }

    // open the file
    ScopedHandle hFile( safe_handle( CreateFile2( fileName,
                                                  GENERIC_READ,
                                                  FILE_SHARE_READ,
                                                  OPEN_EXISTING,
                                                  nullptr ) ) );

    if ( !hFile )
    {
        return HRESULT_FROM_WIN32( GetLastError() );
    }

    // Get the file size
    LARGE_INTEGER FileSize = { 0 };

    FILE_STANDARD_INFO fileInfo;
    if ( !GetFileInformationByHandleEx( hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo) ) )
    {
        return HRESULT_FROM_WIN32( GetLastError() );
    }
    FileSize = fileInfo.EndOfFile;

    // File is too big for 32-bit allocation, so reject read
    if (FileSize.HighPart > 0)
    {
        return E_FAIL;
    }

    // Need at least enough data to fill the header and magic number to be a valid DDS
    if (FileSize.LowPart < ( sizeof(DDS_HEADER) + sizeof(uint32_t) ) )
    {
        return E_FAIL;
    }

    // create enough space for the file data
    ddsData.reset( new (std::nothrow) uint8_t[ FileSize.LowPart ] );
    if (!ddsData)
    {
        return E_OUTOFMEMORY;
    }

    // read the data in
    DWORD BytesRead = 0;
    if (!ReadFile( hFile.get(),
                   ddsData.get(),
                   FileSize.LowPart,
                   &BytesRead,
                   nullptr
                 ))
    {
        return HRESULT_FROM_WIN32( GetLastError() );
    }

    if (BytesRead < FileSize.LowPart)
    {
        return E_FAIL;
    }

    // DDS files always start with the same magic number ("DDS ")
    uint32_t dwMagicNumber = *( const uint32_t* )( ddsData.get() );
    if (dwMagicNumber != DDS_MAGIC)
    {
        return E_FAIL;
    }

    auto hdr = reinterpret_cast<DDS_HEADER*>( ddsData.get() + sizeof( uint32_t ) );

    // Verify header to validate DDS file
    if (hdr->size != sizeof(DDS_HEADER) ||
        hdr->ddspf.size != sizeof(DDS_PIXELFORMAT))
    {
        return E_FAIL;
    }

    // Check for XBOX extension
    if ( !( hdr->ddspf.flags & DDS_FOURCC )
         || ( MAKEFOURCC( 'X', 'B', 'O', 'X' ) != hdr->ddspf.fourCC ) )
    {
        // Use standard DDSTextureLoader instead
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }

    // Must be long enough for both headers and magic value
    if (FileSize.LowPart < ( sizeof(DDS_HEADER) + sizeof(uint32_t) + sizeof(DDS_HEADER_XBOX) ) )
    {
        return E_FAIL;
    }

    // setup the pointers in the process request
    *header = hdr;
    ptrdiff_t offset = sizeof( uint32_t ) + sizeof( DDS_HEADER ) + sizeof( DDS_HEADER_XBOX );
    *bitData = ddsData.get() + offset;
    *bitSize = FileSize.LowPart - offset;

    return S_OK;
}


//--------------------------------------------------------------------------------------
static DXGI_FORMAT MakeSRGB( _In_ DXGI_FORMAT format )
{
    switch( format )
    {
    case DXGI_FORMAT_R8G8B8A8_UNORM:
        return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;

    case DXGI_FORMAT_BC1_UNORM:
        return DXGI_FORMAT_BC1_UNORM_SRGB;

    case DXGI_FORMAT_BC2_UNORM:
        return DXGI_FORMAT_BC2_UNORM_SRGB;

    case DXGI_FORMAT_BC3_UNORM:
        return DXGI_FORMAT_BC3_UNORM_SRGB;

    case DXGI_FORMAT_B8G8R8A8_UNORM:
        return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;

    case DXGI_FORMAT_B8G8R8X8_UNORM:
        return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB;

    case DXGI_FORMAT_BC7_UNORM:
        return DXGI_FORMAT_BC7_UNORM_SRGB;

    default:
        return format;
    }
}


//--------------------------------------------------------------------------------------
static HRESULT CreateD3DResources( _In_ ID3D11Device1* d3dDevice,
                                   _In_ ID3DXboxPerformanceDevice* perfDevice,
                                   _In_ const DDS_HEADER_XBOX* xboxext,
                                   _In_ uint32_t width,
                                   _In_ uint32_t height,
                                   _In_ uint32_t depth,
                                   _In_ uint32_t mipCount,
                                   _In_ uint32_t arraySize,
                                   _In_ bool forceSRGB,
                                   _In_ bool isCubeMap,
                                   _In_ void* grfxMemory,
                                   _Outptr_opt_ ID3D11Resource** texture,
                                   _Outptr_opt_ ID3D11ShaderResourceView** textureView )
{
    if ( !d3dDevice || !perfDevice || !grfxMemory )
        return E_POINTER;

    HRESULT hr = E_FAIL;

    DXGI_FORMAT format = xboxext->dxgiFormat;
    if ( forceSRGB )
    {
        format = MakeSRGB( format );
    }

    switch ( xboxext->resourceDimension ) 
    {
        case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
            {
                D3D11_TEXTURE1D_DESC desc;
                memset( &desc, 0, sizeof(desc) );
                desc.Width = static_cast<UINT>( width ); 
                desc.MipLevels = static_cast<UINT>( mipCount );
                desc.ArraySize = static_cast<UINT>( arraySize );
                desc.Format = format;
                desc.Usage = D3D11_USAGE_DEFAULT;
                desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;

                ID3D11Texture1D* tex = nullptr;
                hr = perfDevice->CreatePlacementTexture1D( &desc, xboxext->tileMode, 0, grfxMemory, &tex );
                if (SUCCEEDED( hr ) && tex != 0)
                {
                    if (textureView != 0)
                    {
                        D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
                        memset( &SRVDesc, 0, sizeof( SRVDesc ) );
                        SRVDesc.Format = format;

                        if (arraySize > 1)
                        {
                            SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
                            SRVDesc.Texture1DArray.MipLevels = desc.MipLevels;
                            SRVDesc.Texture1DArray.ArraySize = static_cast<UINT>( arraySize );
                        }
                        else
                        {
                            SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
                            SRVDesc.Texture1D.MipLevels = desc.MipLevels;
                        }

                        hr = d3dDevice->CreateShaderResourceView( tex,
                                                                  &SRVDesc,
                                                                  textureView
                                                                );
                        if ( FAILED(hr) )
                        {
                            tex->Release();
                            return hr;
                        }
                    }

                    if (texture != 0)
                    {
                        *texture = tex;
                    }
                    else
                    {
                        SetDebugObjectName(tex, "XboxDDSTextureLoader");
                        tex->Release();
                    }
                }
            }
           break;

        case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
            {
                D3D11_TEXTURE2D_DESC desc;
                memset( &desc, 0, sizeof(desc) );
                desc.Width = static_cast<UINT>( width );
                desc.Height = static_cast<UINT>( height );
                desc.MipLevels = static_cast<UINT>( mipCount );
                desc.ArraySize = static_cast<UINT>( arraySize );
                desc.Format = format;
                desc.SampleDesc.Count = 1;
                desc.Usage = D3D11_USAGE_DEFAULT;
                desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
                desc.MiscFlags = ( isCubeMap ) ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0;

                ID3D11Texture2D* tex = nullptr;
                hr = perfDevice->CreatePlacementTexture2D( &desc, xboxext->tileMode, 0, grfxMemory, &tex );
                if (SUCCEEDED( hr ) && tex != 0)
                {
                    if (textureView != 0)
                    {
                        D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
                        memset( &SRVDesc, 0, sizeof( SRVDesc ) );
                        SRVDesc.Format = format;

                        if ( isCubeMap )
                        {
                            if (arraySize > 6)
                            {
                                SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBEARRAY;
                                SRVDesc.TextureCubeArray.MipLevels = desc.MipLevels;

                                // Earlier we set arraySize to (NumCubes * 6)
                                SRVDesc.TextureCubeArray.NumCubes = static_cast<UINT>( arraySize / 6 );
                            }
                            else
                            {
                                SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
                                SRVDesc.TextureCube.MipLevels = desc.MipLevels;
                            }
                        }
                        else if (arraySize > 1)
                        {
                            SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
                            SRVDesc.Texture2DArray.MipLevels = desc.MipLevels;
                            SRVDesc.Texture2DArray.ArraySize = static_cast<UINT>( arraySize );
                        }
                        else
                        {
                            SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
                            SRVDesc.Texture2D.MipLevels = desc.MipLevels;
                        }

                        hr = d3dDevice->CreateShaderResourceView( tex,
                                                                  &SRVDesc,
                                                                  textureView
                                                                );
                        if ( FAILED(hr) )
                        {
                            tex->Release();
                            return hr;
                        }
                    }

                    if (texture != 0)
                    {
                        *texture = tex;
                    }
                    else
                    {
                        SetDebugObjectName(tex, "XboxDDSTextureLoader");
                        tex->Release();
                    }
                }
            }
            break;

        case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
            {
                D3D11_TEXTURE3D_DESC desc;
                memset( &desc, 0, sizeof(desc) );
                desc.Width = static_cast<UINT>( width );
                desc.Height = static_cast<UINT>( height );
                desc.Depth = static_cast<UINT>( depth );
                desc.MipLevels = static_cast<UINT>( mipCount );
                desc.Format = format;
                desc.Usage = D3D11_USAGE_DEFAULT;
                desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;

                ID3D11Texture3D* tex = nullptr;
                hr = perfDevice->CreatePlacementTexture3D( &desc, xboxext->tileMode, 0, grfxMemory, &tex );
                if (SUCCEEDED( hr ) && tex != 0)
                {
                    if (textureView != 0)
                    {
                        D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
                        memset( &SRVDesc, 0, sizeof( SRVDesc ) );
                        SRVDesc.Format = format;

                        SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
                        SRVDesc.Texture3D.MipLevels = desc.MipLevels;

                        hr = d3dDevice->CreateShaderResourceView( tex,
                                                                  &SRVDesc,
                                                                  textureView
                                                                );
                        if ( FAILED(hr) )
                        {
                            tex->Release();
                            return hr;
                        }
                    }

                    if (texture != 0)
                    {
                        *texture = tex;
                    }
                    else
                    {
                        SetDebugObjectName(tex, "XboxDDSTextureLoader");
                        tex->Release();
                    }
                }
            }
            break; 
    }

    return hr;
}


//--------------------------------------------------------------------------------------
static HRESULT CreateTextureFromDDS( _In_ ID3D11Device1* d3dDevice,
                                     _In_ ID3DXboxPerformanceDevice* perfDevice,
                                     _In_ const DDS_HEADER* header,
                                     _In_reads_bytes_(bitSize) const uint8_t* bitData,
                                     _In_ size_t bitSize,
                                     _In_ bool forceSRGB,
                                     _Outptr_opt_ ID3D11Resource** texture,
                                     _Outptr_opt_ ID3D11ShaderResourceView** textureView,
                                     _Outptr_ void** grfxMemory )
{
    HRESULT hr = S_OK;

    uint32_t width = header->width;
    uint32_t height = header->height;
    uint32_t depth = header->depth;

    uint32_t mipCount = header->mipMapCount;
    if (0 == mipCount)
    {
        mipCount = 1;
    }

    if ( !( header->ddspf.flags & DDS_FOURCC )
         || ( MAKEFOURCC( 'X', 'B', 'O', 'X' ) != header->ddspf.fourCC ) )
    {
        // Use standard DDSTextureLoader instead
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }

    auto xboxext = reinterpret_cast<const DDS_HEADER_XBOX*>( reinterpret_cast<const uint8_t*>( header ) + sizeof(DDS_HEADER) );

#ifndef NDEBUG
    if ( xboxext->xdkVer < _XDK_VER )
    {
        OutputDebugStringA( "WARNING: DDS XBOX file may be outdated and need regeneration\n" );
    }
#endif

    uint32_t arraySize = xboxext->arraySize;
    if ( arraySize == 0 )
    {
        return HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
    }

    bool isCubeMap = false;

    switch ( xboxext->resourceDimension )
    {
    case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
        if ( (header->flags & DDS_HEIGHT) && height != 1 )
        {
            return HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
        }
        height = depth = 1;
        break;

    case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
        if ( xboxext->miscFlag & D3D11_RESOURCE_MISC_TEXTURECUBE )
        {
            arraySize *= 6;
            isCubeMap = true;
        }
        depth = 1;
        break;

    case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
        if ( !(header->flags & DDS_HEADER_FLAGS_VOLUME) )
        {
            return HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
        }

        if ( arraySize > 1 )
        {
            return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
        }
        break;

    default:
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }

    // Bound sizes
    if ( mipCount > D3D11_REQ_MIP_LEVELS )
    {
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }

    switch ( xboxext->resourceDimension )
    {
        case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
            if (( arraySize > D3D11_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION ) ||
                ( width > D3D11_REQ_TEXTURE1D_U_DIMENSION ) )
            {
                return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
            }
            break;

        case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
            if ( isCubeMap )
            {
                // This is the right bound because we set arraySize to (NumCubes*6) above
                if (( arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION ) ||
                    ( width > D3D11_REQ_TEXTURECUBE_DIMENSION ) ||
                    ( height > D3D11_REQ_TEXTURECUBE_DIMENSION ))
                {
                    return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
                }
            }
            else if (( arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION ) ||
                     ( width > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION ) ||
                     ( height > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION ))
            {
                return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
            }
            break;

        case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
            if (( arraySize > 1) ||
                ( width > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION ) ||
                ( height > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION ) ||
                ( depth > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION ) )
            {
                return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
            }
            break;
    }

    if ( xboxext->dxgiFormat == DXGI_FORMAT_UNKNOWN )
    {
        return E_FAIL;
    }

    if ( !xboxext->dataSize || !xboxext->baseAlignment )
    {
        return E_FAIL;
    }

    if ( xboxext->dataSize > bitSize )
    {
        return HRESULT_FROM_WIN32( ERROR_HANDLE_EOF );
    }

    // Allocate graphics memory
    size_t sizeBytes = (size_t( xboxext->dataSize ) + 0xFFF) & ~0xFFF; // 4K boundary
    size_t alignmentBytes = std::max<size_t>( xboxext->baseAlignment, 4096 );

    hr = D3DAllocateGraphicsMemory( sizeBytes, alignmentBytes, 0, D3D11_GRAPHICS_MEMORY_ACCESS_CPU_CACHE_COHERENT, grfxMemory );
    if ( FAILED(hr) )
        return hr;

    assert( *grfxMemory != 0 );

    // Copy tiled data into graphics memory
    memcpy( *grfxMemory, bitData, xboxext->dataSize );

    // Create the texture
    hr = CreateD3DResources( d3dDevice, perfDevice, xboxext,
                             width, height, depth, mipCount, arraySize,
                             forceSRGB, isCubeMap, *grfxMemory,
                             texture, textureView );
    if ( FAILED(hr) )
    {
        D3DFreeGraphicsMemory( *grfxMemory );
        *grfxMemory = nullptr;
    }

    return hr;
}


//--------------------------------------------------------------------------------------
static DDS_ALPHA_MODE GetAlphaMode( _In_ const DDS_HEADER* header )
{
    if ( header->ddspf.flags & DDS_FOURCC )
    {
        if ( MAKEFOURCC( 'X', 'B', 'O', 'X' ) == header->ddspf.fourCC )
        {
            auto xboxext = reinterpret_cast<const DDS_HEADER_XBOX*>( reinterpret_cast<const uint8_t*>( header ) + sizeof(DDS_HEADER) );
            auto mode = static_cast<DDS_ALPHA_MODE>( xboxext->miscFlags2 & DDS_MISC_FLAGS2_ALPHA_MODE_MASK );
            switch( mode )
            {
            case DDS_ALPHA_MODE_STRAIGHT:
            case DDS_ALPHA_MODE_PREMULTIPLIED:
            case DDS_ALPHA_MODE_OPAQUE:
            case DDS_ALPHA_MODE_CUSTOM:
                return mode;
            }
        }
    }

    return DDS_ALPHA_MODE_UNKNOWN;
}


//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Xbox::CreateDDSTextureFromMemory( ID3D11Device1* d3dDevice,
                                          ID3DXboxPerformanceDevice* perfDevice,
                                          const uint8_t* ddsData,
                                          size_t ddsDataSize,
                                          ID3D11Resource** texture,
                                          ID3D11ShaderResourceView** textureView,
                                          void** grfxMemory,
                                          DDS_ALPHA_MODE* alphaMode,
                                          bool forceSRGB )
{
    if ( texture )
    {
        *texture = nullptr;
    }
    if ( textureView )
    {
        *textureView = nullptr;
    }
    if ( alphaMode )
    {
        *alphaMode = DDS_ALPHA_MODE_UNKNOWN;
    }

    if ( !d3dDevice || !perfDevice || !ddsData || (!texture && !textureView) || !grfxMemory )
    {
        return E_INVALIDARG;
    }

    *grfxMemory = nullptr;

    // Validate DDS file in memory
    if (ddsDataSize < (sizeof(uint32_t) + sizeof(DDS_HEADER)))
    {
        return E_FAIL;
    }

    uint32_t dwMagicNumber = *( const uint32_t* )( ddsData );
    if (dwMagicNumber != DDS_MAGIC)
    {
        return E_FAIL;
    }

    auto header = reinterpret_cast<const DDS_HEADER*>( ddsData + sizeof( uint32_t ) );

    // Verify header to validate DDS file
    if (header->size != sizeof(DDS_HEADER) ||
        header->ddspf.size != sizeof(DDS_PIXELFORMAT))
    {
        return E_FAIL;
    }

    // Check for XBOX extension
    if ( !( header->ddspf.flags & DDS_FOURCC )
         || ( MAKEFOURCC( 'X', 'B', 'O', 'X' ) != header->ddspf.fourCC ) )
    {
        // Use standard DDSTextureLoader instead
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }

    // Must be long enough for both headers and magic value
    if (ddsDataSize < (sizeof(DDS_HEADER) + sizeof(uint32_t) + sizeof(DDS_HEADER_XBOX)))
    {
        return E_FAIL;
    }

    ptrdiff_t offset = sizeof( uint32_t ) + sizeof( DDS_HEADER ) + sizeof( DDS_HEADER_XBOX );

    HRESULT hr = CreateTextureFromDDS( d3dDevice, perfDevice, header,
                                       ddsData + offset, ddsDataSize - offset, forceSRGB,
                                       texture, textureView,
                                       grfxMemory );
    if ( SUCCEEDED(hr) )
    {
        if (texture != 0 && *texture != 0)
        {
            SetDebugObjectName(*texture, "XboxDDSTextureLoader");
        }

        if (textureView != 0 && *textureView != 0)
        {
            SetDebugObjectName(*textureView, "XboxDDSTextureLoader");
        }

        if ( alphaMode )
            *alphaMode = GetAlphaMode( header );
    }

    return hr;
}

//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Xbox::CreateDDSTextureFromFile( ID3D11Device1* d3dDevice,
                                        ID3DXboxPerformanceDevice* perfDevice,
                                        const wchar_t* fileName,
                                        ID3D11Resource** texture,
                                        ID3D11ShaderResourceView** textureView,
                                        void** grfxMemory,
                                        DDS_ALPHA_MODE* alphaMode,
                                        bool forceSRGB )
{
    if ( texture )
    {
        *texture = nullptr;
    }
    if ( textureView )
    {
        *textureView = nullptr;
    }
    if ( alphaMode )
    {
        *alphaMode = DDS_ALPHA_MODE_UNKNOWN;
    }

    if ( !d3dDevice || !perfDevice || !fileName || (!texture && !textureView) || !grfxMemory )
    {
        return E_INVALIDARG;
    }

    *grfxMemory = nullptr;

    DDS_HEADER* header = nullptr;
    uint8_t* bitData = nullptr;
    size_t bitSize = 0;

    std::unique_ptr<uint8_t[]> ddsData;
    HRESULT hr = LoadTextureDataFromFile( fileName,
                                          ddsData,
                                          &header,
                                          &bitData,
                                          &bitSize
                                        );
    if (FAILED(hr))
    {
        return hr;
    }

    hr = CreateTextureFromDDS( d3dDevice, perfDevice, header,
                               bitData, bitSize, forceSRGB,
                               texture, textureView,
                               grfxMemory );

    if ( SUCCEEDED(hr) )
    {
#if !defined(NO_D3D11_DEBUG_NAME) && ( defined(_DEBUG) || defined(PROFILE) )
        if (texture != 0 || textureView != 0)
        {
            CHAR strFileA[MAX_PATH];
            int result = WideCharToMultiByte( CP_ACP,
                                              WC_NO_BEST_FIT_CHARS,
                                              fileName,
                                              -1,
                                              strFileA,
                                              MAX_PATH,
                                              nullptr,
                                              FALSE
                               );
            if ( result > 0 )
            {
                const CHAR* pstrName = strrchr( strFileA, '\\' );
                if (!pstrName)
                {
                    pstrName = strFileA;
                }
                else
                {
                    pstrName++;
                }

                if (texture != 0 && *texture != 0)
                {
                    (*texture)->SetPrivateData( WKPDID_D3DDebugObjectName,
                                                static_cast<UINT>( strnlen_s(pstrName, MAX_PATH) ),
                                                pstrName
                                              );
                }

                if (textureView != 0 && *textureView != 0 )
                {
                    (*textureView)->SetPrivateData( WKPDID_D3DDebugObjectName,
                                                    static_cast<UINT>( strnlen_s(pstrName, MAX_PATH) ),
                                                    pstrName
                                                  );
                }
            }
        }
#endif

        if ( alphaMode )
            *alphaMode = GetAlphaMode( header );
    }

    return hr;
}
